#ifndef PRVP_NSShiftPeriodDistance_HPP_
#define PRVP_NSShiftPeriodDistance_HPP_

// Framework includes
#include "../OptFrame/NSSeq.hpp"

// Own includes
#include "ProblemInstance.hpp"
#include "Memory.h"
#include "Solution.h"

using namespace std;

class MoveShiftPeriodDistance: public Move<RepPRVP, MemPRVP>
{
private:
	int periodo;
	//escolha é o indice da escolha no vetor de dados do Problema Teste
	//escolhaReal é o número real da escolha, que deve ser decomposto
	int rota, cliente, escolha, escolhaReal;
	typedef vector<vector<int> > Routes;
	PRVPProblemInstance& pPRVP;

	vector<int>& decompoeEscolha(int escolha)
	{
		vector<int> * periodos = new vector<int> ;
		int p = pPRVP.nPeriod - 1;
		while (escolha != 0)
		{
			if (escolha % 2 == 1)
			{
				periodos->push_back(p);
			}
			escolha = escolha / 2;
			p--; //passando para o proximo periodo
		}

		return *periodos;
	}

	pair<int, int> findCliInPeriod(int id, const Routes& rt)
	{
		for (unsigned int r = 0; r < rt.size(); r++)
			for (unsigned int c = 0; c < rt.at(r).size(); c++)
			{
				if (id == rt.at(r).at(c))
				{
					return make_pair(r, c);
				}
			}

		return make_pair(-1, -1);

	}

	bool addCliRoute(int id, Routes& rt)
	{
		//cout<<"-----------------------------------"<<endl;
		vector<double> demandas = demandaRota(rt);
		//cout<<"id dentro"<<id<<endl;
		double demanda_cli = pPRVP.dados->at(id).at(DEMANDA);
		/*cout<<"demandaCli dentro:"<<demanda_cli<<endl;
		 cout<<"demandas dentro:"<<demandas<<endl;
		 */
		for (unsigned int r = 0; r < rt.size(); r++)
		{
			if (demandas.at(r) + demanda_cli <= pPRVP.capacidade)
			{
				rt.at(r).push_back(id);
				return true; // consegui adicionar
			}
		}

		return false;//não conseguiu adicionar

	}

	bool addCliRoutePeriodo(int id, Routes& rt)
	{
		int rota = rand() % rt.size();
		rt.at(rota).push_back(id);

		return true;
	}

	vector<double> demandaRota(const vector<vector<int> >& rt)
	{
		vector<double> s(rt.size(), 0);
		for (unsigned int r = 0; r < rt.size(); r++)
			for (unsigned int c = 0; c < rt.at(r).size(); c++)
				s.at(r) += pPRVP.dados->at(rt.at(r).at(c)).at(DEMANDA);

		return s;

	}

	double demandaPeriodo(const vector<vector<int> >& rt)
	{
		double soma = 0;
		for (unsigned int r = 0; r < rt.size(); r++)
			for (unsigned int c = 0; c < rt.at(r).size(); c++)
				soma += pPRVP.dados->at(rt.at(r).at(c)).at(DEMANDA);

		return soma;

	}

public:

	using Move<RepPRVP, MemPRVP>::apply; // prevents name hiding
	using Move<RepPRVP, MemPRVP>::canBeApplied; // prevents name hiding

	MoveShiftPeriodDistance(PRVPProblemInstance& _problemInstance, int _p, int _r, int _c, int _escolha) :
		pPRVP(_problemInstance), periodo(_p), rota(_r), cliente(_c), escolha(_escolha)
	{
	}

	virtual ~MoveShiftPeriodDistance()
	{
	}

	bool estaNaLista(int i, vector<int> a)
	{
		for (int k = 0; k < a.size(); k++)
			if (i == a.at(k))
				return true;
		return false;
	}

	bool canBeApplied(const MemPRVP& ads, const RepPRVP& rep)
	{
		escolhaReal = pPRVP.dados->at(rep.at(periodo).at(rota).at(cliente)).at((NCOMB + 1) + escolha);
		//verificando se o cliente cabe nos periodos escolhidos
		int c = rep.at(periodo).at(rota).at(cliente);
		vector<int> pEscolhidos = decompoeEscolha(escolhaReal);

		vector<bool> novaEscolha(pPRVP.nPeriod, false);
		for (int i = 0; i < pEscolhidos.size(); i++)
			novaEscolha[pEscolhidos[i]] = true;

		vector<bool> atualEscolha(pPRVP.nPeriod, false);
		//vetor que armazena a posição do cliente em cada um dos periodos que ele está presente atualmente
		for (int i = 0; i < ads.getLocal_cli().at(c).size(); i++)
			atualEscolha.at(ads.getLocal_cli().at(c).at(i)) = true;

		/*if (novaEscolha == atualEscolha)
		 {
		 invalidos++;
		 return false;
		 }*/

		//vector<double> demandas;
		double demandaP;
		double demanda_cli = pPRVP.dados->at(c).at(DEMANDA);

		bool cabe;

		for (int p = 0; p < novaEscolha.size(); ++p)
		{
			demandaP = demandaPeriodo(rep.at(p));

			if (novaEscolha[p] && !atualEscolha[p])
			{
				cabe = false;

				if (demandaP + demanda_cli <= pPRVP.nVeic * pPRVP.capacidade)
					cabe = true;

				if (!cabe)
				{
					invalidos++;
					return false;
				}
			}

		}
		validos++;

		return true;

	}

	bool canBeApplied(const RepPRVP& rep)
	{
		cout << "ENTROU##################################################" << endl;
		exit(1);
		return true;

	}

	Move<RepPRVP, MemPRVP>& apply(RepPRVP& rep)
	{
		cout << "ENTROU##################################################2" << endl;
		exit(1);
		return *new MoveShiftPeriod(pPRVP, 0, 0, 0, 0);// SEM MOVIMENTO REVERSO
	}

	Move<RepPRVP, MemPRVP>& apply(MemPRVP& ads, RepPRVP& rep)
	{
		int c = rep.at(periodo).at(rota).at(cliente); // id do cliente

		vector<int> pEscolhidos = decompoeEscolha(escolhaReal); //periodos que o cliente fará parte após o movimento

		vector<bool> novaEscolha(pPRVP.nPeriod, false);
		for (int i = 0; i < pEscolhidos.size(); i++)
			novaEscolha[pEscolhidos[i]] = true;

		vector<bool> atualEscolha(pPRVP.nPeriod, false);

		//vetor que armazena a posição do cliente em cada um dos periodos que ele está presente atualmente

		vector<pair<int, pair<int, int> > > posCli;
		for (unsigned int p = 0; p < pPRVP.nPeriod; p++)
		{
			pair<int, int> rc = findCliInPeriod(c, rep.at(p));
			if (rc.first > -1)
			{
				posCli.push_back(make_pair(p, rc));
				atualEscolha[p] = true;
			}
		}
		//removendo todos os clientes iguais ao cliente do movimento
		for (unsigned int p = 0; p < posCli.size(); p++)
		{

			int pp = posCli.at(p).first;
			int rr = posCli.at(p).second.first;
			int cc = posCli.at(p).second.second;
			rep.at(pp).at(rr).erase((rep.at(pp).at(rr).begin() + cc));

		}

		for (unsigned int p = 0; p < pEscolhidos.size(); p++)
		{
			if (!addCliRoutePeriodo(c, rep.at(pEscolhidos.at(p))))
			{
				cout << "############################" << endl;
				cout << "###    FUDEU              ##" << endl;
				cout << "############################" << endl;
				exit(1);
			}
		}

		//atualizando a memória
		ads.getLocal_cli().at(c) = pEscolhidos;

		return *new MoveShiftPeriod(pPRVP, 0, 0, 0, 0);// SEM MOVIMENTO REVERSO
	}

	virtual bool operator==(const Move<RepPRVP, MemPRVP>& _m) const
	{
		const MoveShiftPeriodDistance& m = (const MoveShiftPeriodDistance&) _m;
		return (periodo == m.periodo) && (rota == m.rota) && (cliente == m.cliente) && (escolha == m.escolha);
	}

	void print() const
	{
		cout << "MoveShiftPeriodDistance( ";
		cout << periodo << ", " << rota << " ," << cliente << ", " << escolha << " )" << endl;
	}
};

class NSShiftPeriodDistance: public NS<RepPRVP, MemPRVP>
{
private:
	PRVPProblemInstance& problemInstance;
	vector<vector<pair<int,double> > >& cli_dist;
public:

	using NS<RepPRVP, MemPRVP>::move; // prevents name hiding

	NSShiftPeriodDistance(PRVPProblemInstance& _problemInstance, vector<vector<pair<int,double> > >& _cli_dist) :
		problemInstance(_problemInstance), cli_dist(_cli_dist)
	{
	}

	virtual ~NSShiftPeriodDistance()
	{
	}

	virtual Move<RepPRVP, MemPRVP>& move(const RepPRVP& rep)
	{
		int period;
		bool one_valid;
		do
		{
			period = rand() % rep.size();
			one_valid = false;
			for (int i = 0; i < rep.at(period).size(); i++)
			{
				if (rep.at(period).at(i).size() > 0)
					one_valid = true;
			}
		} while (!one_valid);

		int rota;
		do
		{
			rota = rand() % rep.at(period).size();
		} while (rep.at(period).at(rota).size() == 0);
		int cliente = rand() % rep.at(period).at(rota).size();
		int escolha = rand() % problemInstance.dados->at(rep.at(period).at(rota).at(cliente)).at(NCOMB);

		return *new MoveShiftPeriodDistance(problemInstance, period, rota, cliente, escolha); // return a random move
	}

	virtual void print()
	{
		cout << "NSShiftPeriodDistance" << endl;
	}
};

#endif /*PRVP_NSShiftPeriodDistance_HPP_*/

